﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Media;
using Expect.VisualStudio.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;

namespace Expect.Vs2010.Extensions.Tagging
{
    class SquiggleTagger : ITagger<IErrorTag>, IDisposable
    {
        private string _attachedFilename;
        private ITextBuffer _buffer;
        private ITextView _textView;
        private ITagAggregator<IFixtureTag> _fixtureAggregator;
        private bool disposed = false;
        internal const string SpellingErrorType = "Fixture not found";
        
        /// <summary>
        /// Defines colors for the spelling squiggles.
        /// </summary>
        [Export(typeof(EditorFormatDefinition))]
        [Name(SquiggleTagger.SpellingErrorType)]
        [Order(After = Priority.High)]
        [UserVisible(true)]
        internal class SpellingErrorClassificationFormatDefinition : EditorFormatDefinition
        {
            public SpellingErrorClassificationFormatDefinition()
            {
                this.ForegroundBrush = new SolidColorBrush(Color.FromRgb(149, 23, 149));
                this.BackgroundCustomizable = false;
                this.DisplayName = "Fixture not found";
            }
        }

        public SquiggleTagger(ITextBuffer buffer, ITextView textView, ITagAggregator<IFixtureTag> misspellingAggregator)
        {
            _buffer = buffer;
            _textView = textView;
            _fixtureAggregator = misspellingAggregator;
            _fixtureAggregator.TagsChanged += ProcessTags;
            IExpectVSService expectService = (IExpectVSService)Package.GetGlobalService(typeof(SExpectVSService));
            var item = expectService.GetCurrentItemResolution();
            if (item != null)
                _attachedFilename = item.FileName;
            expectService.OnSelectedFileChanged += new EventHandler(expectService_OnSelectedFileChanged);
        }

        void expectService_OnSelectedFileChanged(object sender, EventArgs e)
        {
            var item = (sender as ExpectItemResolution);
            if (item.FileName == _attachedFilename)
                RefreshAll();
        }

        private void RefreshAll()
        {
            foreach (var textViewLine in this._textView.TextViewLines)
            {
                SnapshotSpan span = new SnapshotSpan(textViewLine.Snapshot, new Span(textViewLine.Extent.Start, textViewLine.Extent.Length));
                this.RaiseTagsChangedEvent(span);
            }
        }

        private void ProcessTags(object sender, TagsChangedEventArgs args)
        {
            foreach (var span in args.Span.GetSpans(_buffer))
                RaiseTagsChangedEvent(span);
        }

        /// <summary>
        /// Returns tags on demand.
        /// </summary>
        /// <param name="spans">Spans collection to get tags for.</param>
        /// <returns>Squiggle tags in provided spans.</returns>
        public IEnumerable<ITagSpan<IErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans)
        {
            if (spans.Count == 0)
                yield break;

            ITextSnapshot snapshot = spans[0].Snapshot;

            foreach (var fixture in _fixtureAggregator.GetTags(spans))
            {
                _attachedFilename = fixture.Tag.Resolution.Context.FileName;

                if (fixture.Tag.Resolution.FixtureResKind == FixtureResolutionKind.Unknown)
                {
                    var misspellingSpans = fixture.Span.GetSpans(snapshot);
                    if (misspellingSpans.Count != 1) continue;

                    SnapshotSpan errorSpan = misspellingSpans[0];

                    yield return
                        new TagSpan<IErrorTag>(
                            errorSpan, new FixtureSquiggleTag(SquiggleTagger.SpellingErrorType, errorSpan.GetText()));
                }
            }
        }

        public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
        
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    IExpectVSService expectService = (IExpectVSService)Package.GetGlobalService(typeof(SExpectVSService));
                    expectService.OnSelectedFileChanged -= new EventHandler(expectService_OnSelectedFileChanged);

                    _fixtureAggregator.TagsChanged -= ProcessTags;
                    _fixtureAggregator.Dispose();
                }

                disposed = true;
            }
        }

        private void RaiseTagsChangedEvent(SnapshotSpan subjectSpan)
        {
            EventHandler<SnapshotSpanEventArgs> handler = this.TagsChanged;
            if (handler != null)
            {
                handler(this, new SnapshotSpanEventArgs(subjectSpan));
            }
        }
    }
}
